/*
 * A process-shared Read-Write lock that is based on semaphores.
 */

#include "rwlock.h"
#include <string.h>
#include <fcntl.h>
#include <sched.h>
#include <stdlib.h>

#define WLOCK_POSTFIX "_WLOCK"
#define RLOCK_POSTFIX "_RLOCK"


static char* get_sem_name(const char *name, const char *postfix)
{
	char *lock_name = (char *)malloc(sizeof(char) * (strlen(name) + strlen(postfix)));

	strcpy(lock_name, name);
	strcat(lock_name, postfix);

	return lock_name;
}


rwlock_t* rwlock_create(const char *name)
{
	char *wlock_name = get_sem_name(name, WLOCK_POSTFIX);
	char *rlock_name = get_sem_name(name, RLOCK_POSTFIX);
	rwlock_t *res;

	// Create the semaphores names
	strcpy(rlock_name, name);
	strcat(rlock_name, RLOCK_POSTFIX);

	// Create the rwlock structure
	res = (rwlock_t *)malloc(sizeof(rwlock_t));
	// Create the two seamphores (if they aren't already there)
	// Creates a write binary semaphore with initial value of 1, and a read counting semaphore, with the initial value of 0
	res->writesem = sem_open(wlock_name, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 1);
	res->readsem = sem_open(rlock_name, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0);
	

	free(rlock_name);
	free(wlock_name);

	return res;
}

void rwlock_close(rwlock_t *lock)
{
	sem_close(lock->writesem);
	sem_close(lock->readsem);
	free(lock);
}

void rwlock_destroy(const char *name)
{
	char *wlock_name = get_sem_name(name, WLOCK_POSTFIX);
	char *rlock_name = get_sem_name(name, RLOCK_POSTFIX);

	sem_unlink(wlock_name);
	sem_unlink(rlock_name);
	
	free(rlock_name);
	free(wlock_name);
}


void rwlock_read_lock(rwlock_t *lock)
{
	// Make sure that no writer is working, and increase the readers count
	sem_wait(lock->writesem);
	sem_post(lock->readsem);
	sem_post(lock->writesem);
}

void rwlock_read_unlock(rwlock_t *lock)
{
	// Decrease the readers count
	sem_wait(lock->readsem);
}

void rwlock_write_lock(rwlock_t *lock)
{
	int res;
	sem_wait(lock->writesem);
	// Verify that all current readers finish execution
	sem_getvalue(lock->readsem, &res);
	while (res > 0)
	{
		sched_yield();
		sem_getvalue(lock->readsem, &res);
	}
}


void rwlock_write_unlock(rwlock_t *lock)
{
	// Release the writer
	sem_post(lock->writesem);
}


